package com.wxl.common.serialport

import android.annotation.SuppressLint
import android.content.Context
import android.text.TextUtils
import com.wxl.common.AppContext
import com.wxl.common.util.FileCommon
import com.wxl.common.util.Loog
import com.wxl.common.util.ObservableUtil
import io.reactivex.Observable
import io.reactivex.ObservableOnSubscribe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
import java.io.DataInputStream
import java.io.File
import java.io.FileInputStream
import java.util.*
import java.util.concurrent.TimeUnit

class SerialPortUpdate {

    companion object {

        interface OnSerialPortUpdateCallback {
            fun onCheckQBQSerialPortError(error: String)
            fun onStartUpdateSerialPort()
            fun onFinishUpdateSerialPort()
            fun onLoadUpdateFile()
            fun onSendUpdateData(num: Int)
            fun onSendInputData(num: Int)
        }

        private val m2kDataList = ArrayList<String>() // 每次读取的2k 数据集合

        private val mCRC16List = ArrayList<String>() // 每次读取的2k 数据的CRC16集合

        private var qbqVersion = 0
        private var mDetonatorVersion = "0000"
        private var qbqAssetsPath = ""
        private var mUpgradeAddressNum = 0 //当前升级进度
        private const val mQBQPage = 22 // 起爆器总页数
        private var dataCount = 0
        private var retryDisposable: Disposable? = null
        private var retryCount = 3

        private fun findQBQAssets(context: Context): String {
            val fileNames = context.assets.list("")
            if (fileNames != null && fileNames.isNotEmpty()) {
                fileNames.forEach {
                    if (it.endsWith(".bin") && it.contains("QBQ")) {
                        var name: String = it.substring(it.lastIndexOf("/") + 1)
                        name = name.substring(name.lastIndexOf("_") + 1, name.lastIndexOf("."))
                        qbqVersion = Integer.parseInt(name)
                        return it
                    }
                }
            }
            return ""
        }


        /**
         * 开始检测升级
         */
        fun startCheckUpdate(context: Context, callback: OnSerialPortUpdateCallback){
            retryCount = 3
            checkUpdate(context, callback)
        }


        /**
         * 检查升级
         */
       private fun checkUpdate(context: Context, callback: OnSerialPortUpdateCallback) {
            qbqAssetsPath = findQBQAssets(context)
            if (TextUtils.isEmpty(qbqAssetsPath)) {
                callback.onCheckQBQSerialPortError("没有升级资源文件")
                return
            }
            SerialPortManager.cancelIntervalWork()
            val version = AppContext.appContext.getDetonatorVersion()
            if (version == 0) {//未升级状态，去检查主板版本
                /**
                 * step:1 App首先通过读取升级设备信息数据指令，获取升级设备当前软件版本，确定当前设备是否需要升级；
                 */
                SerialD.registerControlVersionSerial(object : Serial() {
                    override fun loadReceiveData(data: String) {
                        val version = Integer.valueOf(data.substring(12, 16), 16)
                        AppContext.appContext.saveDeviceVersion(version)
                        releaseRetry()
                        if (qbqVersion > version) {
                            doSerialPortUpdate(callback)
                        } else {
                            callback.onFinishUpdateSerialPort()
                        }
                    }

                })
                SerialPortManager.send(SerialCmd.GET_CONTROL_VERSION)
                retryUpdateCountDown(context, callback)
            } else {//升级未完成状态,直接升级
                doSerialPortUpdate(callback)
            }

            ///loadSerialShotCircuitData()

        }

        /**
         * 获取版本卡死，重试3次
         */
        private fun retryUpdateCountDown(context: Context, callback: OnSerialPortUpdateCallback) {
            releaseRetry()
            retryDisposable = Observable.timer(3000, TimeUnit.MILLISECONDS)
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    if (retryCount > 0) {
                        signOutUpdate(callback, true)
                        checkUpdate(context, callback)
                    } else {
                        signOutUpdate(callback, true)
                        callback.onCheckQBQSerialPortError("升级失败")
                    }
                    retryCount--
                }
        }

        /**
         * 关闭重试
         */
        private fun releaseRetry() {
            if (retryDisposable != null) {
                retryDisposable?.dispose()
            }
        }


        /**
         * 发送进入升级指令
         * step:2
         * 首先App应向需要升级的设备发送进入升级模式指令，
         * 此时升级设备退出正常运行模式，跳转到bootloader状态，并向App返回ACK；
         */
        private fun doSerialPortUpdate(callback: OnSerialPortUpdateCallback) {
            callback.onStartUpdateSerialPort()
            SerialPortManager.send(SerialCmd.INTO_UPGRADE_LEFT + SerialCmd.mQBQType + SerialCmd.INTO_UPGRADE_RIGHT)
            Serials.registerSingleStartHexSerial(
                SerialCode.CODE_COLLECTOR_DETONATOR_UPDATE_INFO,
                object : Serial() {
                    override fun loadReceiveData(data: String) {
                        val upgrade = isFirmWareInOrOuUpgrade(data)
                        if (upgrade) {
                            doUpdate(data, callback)
                        } else {
                            signOutUpdate(callback, false)
                        }
                    }
                })
        }


        /**
         * 退出升级模式
         */
        private fun signOutUpdate(callback: OnSerialPortUpdateCallback, isError: Boolean) {
            Serials.unRegisterNoKeySerial("serialUpdate")
            mCRC16List.clear()
            m2kDataList.clear()
            Serials.registerSingleStartHexSerial(SerialCode.CODE_COLLECTOR_DETONATOR_UPDATE_EXIT,
                object : Serial() {
                    override fun loadReceiveData(data: String) {
                        SerialPortManager.startQueryPortWork()
                        AppContext.appContext.saveDetonatorVersion(0)
                        if (!isError) {
                            AppContext.appContext.saveDeviceVersion(qbqVersion)
                            callback.onFinishUpdateSerialPort()
                        }
                    }
                })
            SerialPortManager.send(SerialCmd.OUT_UPGRADE_LEFT + SerialCmd.mQBQType + SerialCmd.OUT_UPGRADE_RIGHT + mDetonatorVersion)
        }


        /**
         * 升级操作
         */
        private fun doUpdate(data: String, callback: OnSerialPortUpdateCallback) {
            val state: String = data.substring(8, 10)
            if (TextUtils.equals("81", state)) {//成功
                val identity: String = data.substring(12, 14) // 01 进入升级  02 退出升级
                val type: String = data.substring(4, 6) // B0 为起爆器，C0 为采集器
                if (TextUtils.equals(SerialCmd.mQBQType, type)) {
                    if (TextUtils.equals("01", identity)) {
                        AppContext.appContext.saveDetonatorVersion(1)//保存为未升级完成状态
                        mDetonatorVersion = SerialUtil.getCurrentDetonatorNum(qbqVersion).toString()
                        if (data.length >= 20) {
                            readAssetQBQFile(type, callback)
                        } else {//升级出错,退出升级
                            signOutUpdate(callback, true)
                            callback.onCheckQBQSerialPortError("进入升级返回ACK长度错误")
                        }
                    } else if (TextUtils.equals("02", type)) {//收到退出升级信息
                        AppContext.appContext.saveDetonatorVersion(0)
                        AppContext.appContext.saveDeviceVersion(qbqVersion)
                        callback.onFinishUpdateSerialPort()
                    } else {
                        signOutUpdate(callback, true)
                        callback.onCheckQBQSerialPortError("进入升级状态错误(code:$identity)")
                    }
                }
            } else {//失败
                signOutUpdate(callback, true)
                callback.onCheckQBQSerialPortError("进入升级状态错误(state:$state)")
                Loog.e("升级失败，状态码:$state")
            }
        }


        /**
         * 读出qbq资源进行升级操作
         * step:3 确认需要升级后，App应下载相应的升级包，升级设备分为起爆器和采集器，
         * 这2个程序不同时更新，因此需要单独判断，升级包中包含对应的数据，如程序地址、每扇区字节数等；
         */
        private fun readAssetQBQFile(type: String, callback: OnSerialPortUpdateCallback) {
            callback.onLoadUpdateFile()
            FileCommon.readAssetsInFile(qbqAssetsPath,
                object : ObservableUtil.Companion.OnNextCallback<File>() {
                    override fun onNext(t: File) {
                        ObservableUtil.doNext(ObservableOnSubscribe<File> {
                            DataInputStream(FileInputStream(t)).use { dataStream ->
                                val block = ByteArray(2048)
                                while (dataStream.read(block) != -1) {
                                    m2kDataList.add(ByteUtil.bytesToHexString(block))
                                    mCRC16List.add(ByteUtil.getCRC_16(block))
                                }
                                dataStream.close()
                                it.onNext(File(qbqAssetsPath))
                            }
                        }, object : ObservableUtil.Companion.OnNextCallback<File>() {
                            override fun onNext(t: File) {
                                SerialPortManager.cancelIntervalWork()
                                sendCRC16Data(type, callback)
                            }

                            override fun error(e: Throwable) {
                                e.printStackTrace()
                                Serials.unRegisterNoKeySerial("serialUpdate")
                                callback.onCheckQBQSerialPortError("读取升级文件错误")
                                signOutUpdate(callback, true)
                            }
                        })
                    }
                })

        }


        /**
         * 推送升级信息,返回起爆器升级地址
         * step :4
         * 接着，App应读取一个扇区的升级2K数据，然后发送地址指令，告诉升级设备本扇区数据的起始地址，
         * 等待升级设备返回ACK后，App接着将读到的一个扇区的数据分成8个数据包，发送到升级设备，
         * 每个数据包大小为257个字节，其中1个字节数据包序号，其余256个字节为真实升级数据；扇区总大小为2K，
         * 则数据包编号从0-7；数据包编号按顺序发送，每发送一帧数据，数据包编号加1。扇区数据发送过程中，
         * 升级设备不返回任何数据，但要注意每个数据包中间应间隔40ms，否则会导致丢包，待本扇区数据发送完成后，
         * App发送写入指令，如果升级设备接收到的数据错误或者写入失败，则返回错误，此时App应重新执行步骤4，
         * 重发所有地址和数据，如全部正确则起爆器将本扇区数据写入芯片后并校验，正确后返回ACK；
         */
        private fun sendCRC16Data(type: String, callback: OnSerialPortUpdateCallback) {
            if (mCRC16List.isNotEmpty() && mCRC16List.size > mUpgradeAddressNum) {
                callback.onSendUpdateData(mUpgradeAddressNum)
                val detonatorNum = SerialUtil.getCurrentDetonatorNum(mUpgradeAddressNum)
                Loog.e("mCRC16List[mUpgradeAddressNum]:${mCRC16List[mUpgradeAddressNum]}")
                var cmd =
                    SerialCmd.SEND_UPGRADE_ADDRESS_LEFT + type + SerialCmd.SEND_UPGRADE_ADDRESS_MIDDLE + detonatorNum + SerialCmd.SEND_UPGRADE_ADDRESS_RIGHT + mCRC16List[mUpgradeAddressNum]
                Serials.registerSingleStartHexSerial(SerialCode.CODE_COLLECTOR_DETONATOR_UPDATE_ADR,
                    object : Serial() {
                        override fun loadReceiveData(data: String) {
                            if (data.length >= 14) {
                                send2kData(data, callback)
                            }
                        }
                    })
                SerialPortManager.send(cmd)
            } else {
                signOutUpdate(callback, false)
            }
        }


        /***
         * 发送升级数据
         * step:5
         * App接着将读到的一个扇区的数据分成8个数据包，发送到升级设备，
         * 每个数据包大小为257个字节，其中1个字节数据包序号，其余256个字节为真实升级数据；扇区总大小为2K，
         * 则数据包编号从0-7；数据包编号按顺序发送，每发送一帧数据，数据包编号加1。扇区数据发送过程中，
         * 升级设备不返回任何数据，但要注意每个数据包中间应间隔40ms，否则会导致丢包，待本扇区数据发送完成后，
         * App发送写入指令，如果升级设备接收到的数据错误或者写入失败，则返回错误，此时App应重新执行步骤4，
         * 重发所有地址和数据，如全部正确则起爆器将本扇区数据写入芯片后并校验，正确后返回ACK；
         */
        private fun send2kData(data: String, callback: OnSerialPortUpdateCallback) {
            val state: String = data.substring(8, 10)
            val type: String = data.substring(4, 6) // B0 为起爆器，C0 为采集器
            if (TextUtils.equals("81", state)) {
                if (m2kDataList.isNotEmpty() && m2kDataList.size >= 8) {
                    dataCount = 0
                    delaySend2KData(type, callback)
                }
            } else if (TextUtils.equals("82", state)) {//返回状态跳过，进行下一步
                if (m2kDataList.size > mUpgradeAddressNum) {
                    checkUpdatePage(type, callback)
                }
            } else {
                sendCRC16Data(type, callback)
            }
        }


        /**
         * 数据包编号从0-7；数据包编号按顺序发送，每发送一帧数据，数据包编号加1。扇区数据发送过程中，
         * 升级设备不返回任何数据，但要注意每个数据包中间应间隔40ms，否则会导致丢包，待本扇区数据发送完成后，
         * App发送写入指令，如果升级设备接收到的数据错误或者写入失败，则返回错误，此时App应重新执行步骤4，
         * 重发所有地址和数据，如全部正确则起爆器将本扇区数据写入芯片后并校验，正确后返回ACK；
         */
        @SuppressLint("CheckResult")
        private fun delaySend2KData(type: String, callback: OnSerialPortUpdateCallback) {
            if (dataCount == 8) {
                sendInputData(type, callback)
                return
            }
            Observable.timer(60, TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .map {
                    val str = "0" + dataCount + m2kDataList.get(mUpgradeAddressNum)
                        .substring(512 * dataCount, 512 * (dataCount + 1)).toUpperCase()
                    SerialPortManager.sendHexData(str)
                    ++dataCount
                }.subscribe(Consumer {
                    if (it < 9) {
                        delaySend2KData(type, callback)
                    }
                })
        }


        /**
         * 待本扇区数据发送完成后，
         * App发送写入指令，如果升级设备接收到的数据错误或者写入失败，则返回错误，此时App应重新执行步骤4，
         * 重发所有地址和数据，如全部正确则起爆器将本扇区数据写入芯片后并校验，正确后返回ACK；
         */
        private fun sendInputData(type: String, callback: OnSerialPortUpdateCallback) {
            callback.onSendInputData(mUpgradeAddressNum)
            val inputCmd =
                SerialCmd.SEND_INPUT_LEFT + type + SerialCmd.SEND_INPUT_RIGHT
            Serials.registerSingleStartHexSerial(SerialCode.CODE_COLLECTOR_DETONATOR_UPDATE_INPUT,
                object : Serial() {
                    override fun loadReceiveData(data: String) {
                        loadUpdateInputData(data, callback)
                    }
                })
            SerialPortManager.send(inputCmd)
        }


        /**
         * 写入升级指令返回
         * 升级设备接收到的数据错误或者写入失败，则返回错误，此时App应重新执行步骤4，
         * 重发所有地址和数据，如全部正确则起爆器将本扇区数据写入芯片后并校验，正确后返回ACK；
         */
        private fun loadUpdateInputData(data: String, callback: OnSerialPortUpdateCallback) {
            if (data.length >= 14) {
                val state: String = data.substring(8, 10)
                val type: String = data.substring(4, 6) // B0 为起爆器，C0 为采集器
                if (TextUtils.equals(SerialCmd.mQBQType, type)) {
                    if (TextUtils.equals("81", state)) {
                        checkUpdatePage(type, callback)
                    } else {
                        sendCRC16Data(type, callback)
                    }
                }
            }
        }


        /**
         * 检查升级页数
         */
        private fun checkUpdatePage(type: String, callback: OnSerialPortUpdateCallback) {
            Loog.e("mUpgradeAddressNum:$mUpgradeAddressNum  mQBQPage:$mQBQPage")
            if (mUpgradeAddressNum >= mQBQPage) {
                signOutUpdate(callback, false)
            } else {
                mUpgradeAddressNum++
                sendCRC16Data(type, callback)
            }
        }


        /**
         * 是否是固件进入或者退出升级
         */
        private fun isFirmWareInOrOuUpgrade(serialPortData: String): Boolean {
            var isFlag = false
            if (!TextUtils.isEmpty(serialPortData) && serialPortData.length >= 12) {
                val code = serialPortData.substring(4, 6)
                if (code == "B0") {
                    isFlag = true
                } else if (code == "C0") {
                    isFlag = true
                }
            }
            return isFlag
        }

    }

}